package com.dao;

import com.crypto.Sha256;
import com.google.protobuf.ProtocolStringList;
import com.protos.Peer.*;
import org.apache.commons.codec.binary.Hex;

import java.sql.*;
import java.util.List;

/**
 * 保存数据 block and Transaction
 * @author zyf
 */
public class SyncDao{

    private String lastBlockHash = "";
    private Integer lastHeight = 0;
    private Integer blockCount = 0;
    private Integer transCount = 0;

    private Connection conn;
    private Boolean useTransaction = Boolean.FALSE;
    private String host;

    private static String netSql = "INSERT INTO synclocal values (?,?,?,?,?);";
    private static String queryNet = "SELECT * FROM synclocal  WHERE HOST = ?;";
    private static String updNetSql = "UPDATE synclocal SET syncHeight = ?, lastBlockHash = ?, blockCount = ?, transCount = ? WHERE host = ?;";
    private static String queryBlock = "SELECT * FROM block WHERE HASH = ?";
    private static String blockSql = "INSERT INTO block values (?,?,?,?);";
    private static String queryTran = "SELECT * FROM transaction WHERE TXID = ?";
    private static String tranSql = "INSERT INTO transaction values (?,?,?,?,?,?,?);";

    public SyncDao(Connection conn) {
        this.conn = conn;
    }

    public SyncDao() {
    }

    /**
     * syncDao 初始化，中间表
     * @param host RepChain host地址
     * @throws SQLException
     */
    public void InitSyncDao(String host) throws SQLException {
        this.host = host;
        PreparedStatement searchNetPstmt = conn.prepareStatement(queryNet);
        PreparedStatement netPstmt = conn.prepareStatement(netSql);
        searchNetPstmt.setString(1,host);
        ResultSet rs = searchNetPstmt.executeQuery();
        if (rs.next()) {
            this.lastHeight = rs.getInt("syncHeight");
            this.lastBlockHash = rs.getString("lastBlockHash");
            this.blockCount = rs.getInt("blockCount");
            this.transCount = rs.getInt("transCount");
        } else {
            netPstmt.setString(1,host);
            netPstmt.setInt(2,0);
            netPstmt.setString(3,"");
            netPstmt.setInt(4,0);
            netPstmt.setInt(5,0);
            netPstmt.executeUpdate();
        }
        System.out.println("SyncDao 初始化完成");
    }

    /**
     * 保存块数据
     * @param blk  {@see Peer.Block}
     * @return
     * @throws SQLException
     */
    public Integer saveBlock(Block blk) throws SQLException {

        // 如果使用事务，则setAutoCommit(false)
        conn.setAutoCommit( !this.useTransaction );

        PreparedStatement searchBlockPstmt = conn.prepareStatement(queryBlock);
        PreparedStatement blockPstmt = conn.prepareStatement(blockSql);
        String preHash = "";

        if (!blk.getPreviousBlockHash().isEmpty()) {
            preHash = blk.getPreviousBlockHash().toStringUtf8();
        }

        String blockHash = Hex.encodeHexString(Sha256.hash(blk.toByteArray()));
        Integer blockRs = 0;
        Integer tranRs = 0;
        Integer syncLocalRs = 0;

        // 判断前后块是否可以衔接
        if (preHash.equals(this.lastBlockHash)) {
            // 先查一下该block是否存在
            searchBlockPstmt.setString(1,Hex.encodeHexString(Sha256.hash(blk.toByteArray())));
            ResultSet searchBlockId = searchBlockPstmt.executeQuery();
            if (searchBlockId.next()) {
                System.out.println("该blockId已存在");
                blockRs = 1;
            } else {
                blockPstmt.setString(1, blockHash);
                blockPstmt.setString(2,preHash);
                blockPstmt.setLong(3, blk.getHeight());
                blockPstmt.setInt(4, blk.getTransactionsCount());
//                blockPstmt.setTimestamp(5, new Timestamp(blk.getTimestamp().getSeconds()*1000));
                blockRs = blockPstmt.executeUpdate();
            }
        } else {
            System.out.println("当前块与前一个块hash连不上");
            return 0;
        }

//        System.out.println(blockRs);
        tranRs = saveTransactions(blk.getTransactionsList(),blockHash);
        syncLocalRs = saveSyncLocal(blk.getTransactionsCount(),blockHash);

        //如果使用事务，使用commit提交；否则，不能用commit提交
        if (this.useTransaction) {
            this.conn.commit();
        }
        // 释放连接
//        this.conn.close();

        return blockRs & tranRs & syncLocalRs;
//        BaseDao.closeAll(conn, blockPstmt);
    }

    /**
     * 保存交易
     * @param list 交易list
     * @param blockHash 块hash
     * @throws SQLException
     */
    public Integer saveTransactions(List<Transaction> list, String blockHash) throws SQLException {

        Integer tranRs = 0;

        PreparedStatement searchTranPstmt = conn.prepareStatement(queryTran);
        PreparedStatement tranPstmt = conn.prepareStatement(tranSql);

        for (int i = 0; i < list.size(); i++) {
            Transaction tran = list.get(i);
            // 检查该交易是否存在
            searchTranPstmt.setString(1,tran.getId());
            ResultSet rs =  searchTranPstmt.executeQuery();
            if (rs.next()) {
                System.out.println("表中存在该交易数据: " + tran.getId());
                tranRs = 1;
                continue;
            } else {
                tranPstmt.setString(1,tran.getId());
                tranPstmt.setInt(2,tran.getType().getNumber());
                tranPstmt.setString(3,tran.getCid().getChaincodeName());
                tranPstmt.setInt(4,tran.getCid().getVersion());
                tranPstmt.setString(5,tran.getIpt().getFunction());
                ProtocolStringList protocolStringList = tran.getIpt().getArgsList();
                if (!protocolStringList.isEmpty()) {
                    tranPstmt.setString(6,new String(protocolStringList.get(0).getBytes()));
                } else {
                    tranPstmt.setString(6,null);
                }
                tranPstmt.setString(7,blockHash);
//                tranPstmt.setTimestamp(8,new Timestamp(tran.getTimestamp().getSeconds()*1000));
                System.out.println("save tran " + tran.getId());
                tranRs = tranPstmt.executeUpdate();
            }
        }
        return tranRs;
//        BaseDao.closeAll(conn, tranPstmt);
    }

    /**
     * 更新中间表
     * @param transCount
     * @param blockHash
     */
    public Integer saveSyncLocal(Integer transCount, String blockHash) throws SQLException {

        PreparedStatement netPstmt = conn.prepareStatement(updNetSql);
        netPstmt.setInt(1,this.lastHeight + 1);
        netPstmt.setString(2,blockHash);
        netPstmt.setInt(3,this.blockCount);
        netPstmt.setInt(4,this.transCount + transCount);
        netPstmt.setString(5,this.host);

        if (netPstmt.executeUpdate() == 1) {
            this.transCount = this.transCount + transCount;
            this.lastHeight = this.lastHeight + 1;
            this.lastBlockHash = blockHash;
            return 1;
        }
        return 0;
    }

    public void blockCountAdd() {
        this.blockCount = this.blockCount + 1;
    }

    public String getLastBlockHash() {
        return lastBlockHash;
    }

    public void setLastBlockHash(String lastBlockHash) {
        this.lastBlockHash = lastBlockHash;
    }

    public Integer getLastHeight() {
        return lastHeight;
    }

    public void setLastHeight(Integer lastHeight) {
        this.lastHeight = lastHeight;
    }

    public Integer getBlockCount() {
        return blockCount;
    }

    public void setBlockCount(Integer blockCount) {
        this.blockCount = blockCount;
    }

    public Connection getConn() {
        return conn;
    }

    public void setConn(Connection conn) {
        this.conn = conn;
    }

    public Boolean getUseTransaction() {
        return useTransaction;
    }

    public void setUseTransaction(Boolean useTransaction) {
        this.useTransaction = useTransaction;
    }
}
